package org.mashart.pink.m.gps;

import org.mashart.pink.m.gps.data.NMEAAssembler;
import org.mashart.pink.m.ws.WsConnector;
import org.mashart.pink.m.store.Preferences;

/**
 * Buffer for the Waypoints to be sent out.
 * Store the latest waypoints from receiver, and decide whether to send the data immediately.
 */
public class WaypointsBuffer {

    /**
     * User id
     */
    private String user;

    /**
     * Previous received Waypoint
     */
    private Waypoint preWp;

    /**
     * Approximate distance scale of longitude over latitude.
     * That is, distance of moving towards north/south by 1 degree / distance of moving towards east/west by 1 degree
     */
    private double lonScale;

    /**
     * Old velocity on longitude
     */
    private double oldVelocityLon;

    /**
     * Old velocity on latitude
     */
    private double oldVelocityLat;

    /**
     * Realative threshold on difference between old and current velocity
     */
    private float realativeThres;
    
    /**
     * Distance threshold, equals realativeThres * ( |v_lon| + |v_lat| )
     */
    private double distThres;

    /**
     * Default realative threshold on difference between old and current velocity.
     * Need to be ajusted by looking at the real application.
     */
    final private float DEFAULT_REALATIVE_THRESHOLD = (float)0.2;

    /**
     * Constructor
     */
    public WaypointsBuffer(){
        // initialize
        preWp = null;
        lonScale = 0.0;
        oldVelocityLon = 0.0;
        oldVelocityLat = 0.0;
        distThres = 0.0;

        // set the threshold to default value
        realativeThres = DEFAULT_REALATIVE_THRESHOLD;
        Preferences pref = Preferences.getInstance();
        user = pref.get("user");
    }

    /**
     * Set threshold. Current can only be called before inserting any waypoint.
     */
    public void setThreshold(float threshold){
        realativeThres = threshold;
    }

    /**
     * Get threshold
     */
    public float getThreshold(){
        return realativeThres;
    }

    /**
     * Update with a new received waypoint.
     * The new received waypoint should have a newer timestamp than the previous ones,
     * otherwise, it is discarded.
     *
     */
    public void update(WsConnector ws_client, Waypoint wp){
        // distance between old and current velocity
        double dist;

        // invalid wp
        if(wp == null){
            return;
        }
                    
        // if it is the first received waypoint
        if(preWp == null){
            // get a latitude
            double lat = wp.latitude;
            lonScale = Math.cos( Math.toRadians(lat) );
        }
        else{
            // calculate the elapsed time from previous waypoint to the new received one
            int t = NMEAAssembler.diffTime(wp.timeMS, preWp.timeMS);
            // the new received waypoint is older than the previous one, this should not happen
            if(t <= 0){
                return;
            }
            // the velocity along longitude need to be amended using lonScale, i.e., 1 degree along longitude is as long as along latitude
            double curVelocityLon = (wp.longitude - preWp.longitude) * lonScale / t;
            double curVelocityLat = (wp.latitude - preWp.latitude) / t;

            // calculate the distance between two velocity vectors
            dist = distance(curVelocityLon, curVelocityLat, oldVelocityLon, oldVelocityLat);

            if( dist > distThres ){
                StringBuffer msg = new StringBuffer();
                msg.append("id=").append(user).append("&").append(preWp.toHttpQuery());
                ws_client.push(msg.toString().getBytes());
//                ws_client.push(preWp.getBytes());

                // update the old velocity
                oldVelocityLon = curVelocityLon;
                oldVelocityLat = curVelocityLat;

                // calculate the referenced length, i.e., |v_lon| + |v_lat|
                double ref = distance(curVelocityLon, curVelocityLat, 0, 0);
                // update the distance threshold
                distThres = ref * realativeThres;
            }
        }
        
        // after processing, save the waypoint for the next run.
        preWp = wp;
    }

    /**
     * Finalized the waypoint buffer. Send the last point, and close the web service connection.
     */
    public void finalized(WsConnector ws_client){
        if(preWp != null){
//            ws_client.push(preWp);
            StringBuffer msg = new StringBuffer();
            msg.append("id=").append(user).append("&").append(preWp.toHttpQuery());
            ws_client.push(msg.toString().getBytes());
//            ws_client.push(preWp.getBytes());
        }
    }
    
    /**
     * Calculate the distance between two vectors.
     * Manhatton distance is currently used.
     */
    private double distance(double x1, double y1, double x2, double y2){
        return Math.abs(x1-x2) + Math.abs(y1-y2);
    }
}
